//! import "common\Base.j"
//! import "common\Tree.j"

library ClockSystem initializer Init requires Base, Tree

///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Now()
// PolledSleep(real)
// RegisterCycleEvent(code, code, real)
// StartTimerProcess(function, real)
// StopTimerProcess(integer)
// GetCurrentTimerProcess()
///////////////////////////////////////////////////////////////////////////////////////////////////////////////

function interface TimerProcess takes nothing returns nothing

globals
	private timer ClockTimer
	private real Second = 0
	private integer CycleTop = 1
	private integer array NextCycle
	private trigger array CycleAction
	private real array PRI

	private timer CenterTimer
	private integer MaxProcess = -1
	private integer array Index

	private TimerProcess array Process
	private real array Moment
	private real array Delay
	private integer array Sequence
	private integer Top = 1

	private integer CurrentProcess = 0
endglobals

private constant function ClockPeriod takes nothing returns real
    return 0.25
endfunction

function Now takes nothing returns real
	return Second + TimerGetElapsed(ClockTimer)
endfunction

function PolledSleep takes real time returns nothing//不精确的高效等待，不会提前结束
	local real now = Now()
	set time = time + now
	loop
		exitwhen now >= time
		call TriggerSleepAction(time - now)
		set now = Now()
	endloop
endfunction

private function ClockTimerAction takes nothing returns nothing
	local integer i = 0
	set Second = Second + ClockPeriod()
	loop
		set i = NextCycle[i]
		exitwhen i == 0
		if IsTriggerEnabled(CycleAction[i]) and TriggerEvaluate(CycleAction[i]) then
			call TriggerExecute(CycleAction[i])
		endif
	endloop
endfunction

function RegisterCycleEvent takes code filter, code action, real priority returns nothing
	local integer i
	if CycleTop > GetArrayMaxSize() then
		call Error()
		return
	endif
	set i = 0
	loop
		if NextCycle[i] == 0 or priority > PRI[NextCycle[i]] then
			set CycleAction[CycleTop] = CreateTrigger()
			if filter != null then
				call TriggerAddCondition(CycleAction[CycleTop], Condition(filter))
			endif
			if action != null then
				call TriggerAddAction(CycleAction[CycleTop], action)
			endif
			set PRI[CycleTop] = priority
			set NextCycle[CycleTop] = NextCycle[i]
			set NextCycle[i] = CycleTop
			set CycleTop = CycleTop + 1
			exitwhen true
		endif
		set i = NextCycle[i]
	endloop
endfunction

private function Clear takes integer index returns nothing
	set Process[index] = 0
	set Sequence[index] = Top
	set Top = index
endfunction

private function Push takes integer index returns nothing
	local real moment
	local integer i
	local integer j
	if MaxProcess + 1 > GetArrayMaxSize() then
		call Error()
		return
	endif
	set moment = Moment[index]
	set MaxProcess = MaxProcess + 1
	set i = MaxProcess
	loop
		exitwhen i == 0
		set j = (i - 1) / 2
		if Moment[Index[j]] <= moment then
			exitwhen true
		endif
		set Index[i] = Index[j]
		set Sequence[Index[i]] = i
		set i = j
	endloop
	set Index[i] = index
	set Sequence[index] = i
endfunction

private function Pop takes nothing returns nothing
	local real r = Moment[Index[MaxProcess]]
	local integer i = 0
	local integer j
	loop
		set j = i * 2 + 2
		if j < MaxProcess then
			if Moment[Index[j]] > Moment[Index[j - 1]] then
				set j = j - 1
			endif
		elseif j - 1 < MaxProcess then
			set j = j - 1
		else
			exitwhen true
		endif
		if Moment[Index[j]] >= r then
			exitwhen true
		endif
		set Index[i] = Index[j]
		set Sequence[Index[i]] = i
		set i = j
	endloop
	if i != MaxProcess then
		set Index[i] = Index[MaxProcess]
		set Sequence[Index[i]] = i
	endif
	set MaxProcess = MaxProcess - 1
endfunction

private function CenterTimerAction takes nothing returns nothing
	local real now = Now()
	local integer index
	loop
		exitwhen MaxProcess == -1 or Moment[Index[0]] > now
		set index = Index[0]
		call Pop()
		set Sequence[index] = -1
		set CurrentProcess = index
		call Process[index].evaluate()
		set CurrentProcess = 0
		if Process[index] != 0 then
			if Delay[index] > 0 then
				set Moment[index] = Moment[index] + Delay[index]
				call Push(index)
			else
				call Clear(index)
			endif
		endif
	endloop
	if MaxProcess != -1 then
		call TimerStart(CenterTimer, Moment[Index[0]] - now, false, function CenterTimerAction)
	endif
endfunction

function StartTimerProcess takes TimerProcess process, real delay, real periodic returns integer
	local real now = Now()
	local integer index = Top
	if Sequence[Top] == 0 then
		set Top = Top + 1
	else
		set Top = Sequence[Top]
		set Sequence[index] = -1
	endif
	set Process[index] = process
	set Moment[index] = now + delay
	if periodic > 0 then
		set Delay[index] = periodic
	else
		set Delay[index] = 0
	endif
	call Push(index)
	call TimerStart(CenterTimer, RMaxBJ(Moment[Index[0]] - now, 0), false, function CenterTimerAction)
	return index
endfunction

function StopTimerProcess takes integer index returns nothing
	local integer i
	local integer j
	if Process[index] == 0 then
		return
	endif
	set i = Sequence[index]
	if i != -1 then
		loop
			exitwhen i == 0
			set j = (i - 1) / 2
			set Index[i] = Index[j]
			set Sequence[Index[i]] = i
			set i = j
		endloop
		set Moment[Index[i]] = 0
		call Pop()
	endif
	call Clear(index)
endfunction

private function Init takes nothing returns nothing
	set CenterTimer = CreateTimer()
	set ClockTimer = CreateTimer()
	call TimerStart(ClockTimer, ClockPeriod(), true, function ClockTimerAction)
endfunction

endlibrary